const _ = require('lodash')
const axios = require('axios');
const express = require('express');
const router = require('express').Router();
const models = require('../models');
const RuntimeError = require('../utils/error');
const wrap = require('../utils/wrap');
const logger = require('../utils/logger');
const { quickPaySign } = require('../lib/quickPay');
const { wapPaySign } = require('../lib/wapPay');
const acQueue = require('../lib/account-creation-queue');
const config = require('../config/config.json');
const { estimateFee } = require('../lib/eos');
const { nbhPay } = require('../lib/nbhPay');
const nbhPayNotifyUrl = `${config[process.env.NODE_ENV].serviceUrl}/nbhpaycb`
const { queryActionHisroty } = require('../lib/queryActionHistory');
const appDataCollection = require('../lib/appDataCollection');
const crypto = require('crypto');
const moment = require('moment');
const qrcode = require('qrcode');
const semver = require('semver')

const { insertLog } = require(__base + '/lib/serviceLogger');

router.use("/public", express.static(__base + "/public"))

// 这个是接口时临时用来处理账号创建失败的，把 paid-recreate（这个状态手工修改） 状态重新放到队列中，让重新创建。
router.get('/eosaccount_recreate_for_status_paid', wrap(function* (req, res) {
    logger.info('request eosaccount_recreate_for_status_paid');
    try {
        let list = yield models.AccountCreationRequest.findAll({ where: { status: 'paid-recreate' } });
        if (!list || list.length == 0) {
            res.send(`No data.`);
        }

        for (const acReq of list) {
            acQueue.add(acReq);
        }

        logger.info('eosaccount_recreate_for_status_paid, put in queue->> ', list);

        res.send(`OK!! ${list.length} in queue.`);
    }
    catch (e) {
        if (e instanceof RuntimeError) {
            throw e;
        }
        throw new RuntimeError(500, e.message);
    }

}));


router.get('/pay/nbhpay', wrap(function* (req, res) {
    logger.info('nbhpay', req.query);

    if (!req.query.spbillno) {
        throw new RuntimeError(400, 'Parameter spbillno is missing.');
    }

    try {
        let acReq = yield models.AccountCreationRequest.findOne({ where: { billNo: req.query.spbillno } });
        if (!acReq) {
            throw new RuntimeError(404, 'Bill not found.');
        }

        // Estimate fee of creating account
        // 为了适应estimateFee函数返回值的修改
        let { totalCostCNY: fee } = yield estimateFee();

        // Billing
        let resp = yield nbhPay(req.query.spbillno, fee, nbhPayNotifyUrl);
        logger.info(resp);

        res.render('nbhpay', {
            targetUrl: resp.openUrl,
            fields: resp.fields
        });
    }
    catch (e) {
        if (e instanceof RuntimeError) {
            throw e;
        }
        throw new RuntimeError(500, e.message);
    }
}));

/**
 * The payment callback, we should verify the signature and check
 */
router.post('/quickpaycb', wrap(function* (req, res) {
    const io = req.app.get('io');

    if (!req.body.xml) {
        logger.error('paycb: XML missing.');
        throw new RuntimeError(400, 'XML missing.')
    }

    logger.info(req.body.xml);

    let keys = Object.keys(req.body.xml);
    let data = {};
    for (let k of keys) {
        if (k === 'sign') {
            continue;
        }
        data[k] = req.body.xml[k][0];
    }
    let signature = quickPaySign(data);
    logger.info('My signature:', signature);
    insertLog('info', `quickpay ${data.spbillno} ${signature}`)

    if (signature === req.body.xml.sign[0]) {
        if (req.body.xml.retcode != 0) {
            io.sendPaidResult(data.spbillno, { code: req.body.xml.retcode, message: 'request failed!' });
            return res.send('success');
        }

        // result = 1, done
        // result = 2, processing
        // result = 3, fail
        if (req.body.xml.result == 1) {
            try {
                let acReq = yield models.AccountCreationRequest.findOne({ where: { billNo: data.spbillno } });
                acReq.status = 'paid';
                yield acReq.save();

                acQueue.add(acReq);

                logger.info('Order paid:', acReq.get({ plain: true }));
                io.sendPaidResult(data.spbillno, { code: 0, message: 'success' });
                return res.send('success');
            }
            catch (e) {
                logger.error(e.message);
                insertLog('error', `${data.spbillno} ${e.message}`)
                io.sendPaidResult(data.spbillno, { code: 999, message: e.message });
                return res.send('fail');
            }
        } else {
            if (req.body.xml.result == 3) {
                logger.error('Payment failed!');
                insertLog('error', `${data.spbillno} Payment failed!`)
                io.sendPaidResult(data.spbillno, { code: 999, message: req.body.xml.retmsg });
            }
            return res.send('success');
        }
    } else {
        logger.error('Signature invalid!', req.body);
        insertLog('error', `${data.spbillno} Signature invalid! ${JSON.stringify(req.body)}`)
        io.sendPaidResult(data.spbillno, { code: 999, message: 'Signature invalid!' });
        return res.send('fail');
    }
}));

/**
 * The payment callback, we should verify the signature and check
 */
router.post('/wappaycb', wrap(function* (req, res) {
    const io = req.app.get('io');

    if (!req.body.xml) {
        logger.error('paycb: XML missing.');
        throw new RuntimeError(400, 'XML missing.')
    }

    logger.info(req.body.xml);

    let keys = Object.keys(req.body.xml);
    let data = {};
    for (let k of keys) {
        if (k === 'sign') {
            continue;
        }
        data[k] = req.body.xml[k][0];
    }
    let signature = wapPaySign(data);
    logger.info('My signature:', signature);
    insertLog('info', `wappay ${data.spbillno} ${signature}`)

    if (signature === req.body.xml.sign[0]) {
        if (req.body.xml.retcode != 0) {
            io.sendPaidResult(data.spbillno, { code: req.body.xml.retcode, message: 'request failed!' });
            return res.send('success');
        }

        // result = 1, done
        // result = 2, processing
        // result = 3, fail
        if (req.body.xml.result == 1) {
            try {
                let acReq = yield models.AccountCreationRequest.findOne({ where: { billNo: data.spbillno } });
                acReq.status = 'paid';
                yield acReq.save();

                acQueue.add(acReq);

                logger.info('Order paid:', acReq.get({ plain: true }));
                io.sendPaidResult(data.spbillno, { code: 0, message: 'success' });
                return res.send('success');
            }
            catch (e) {
                logger.error(e.message);
                insertLog('error', `${data.spbillno} ${e.message}`)
                io.sendPaidResult(data.spbillno, { code: 999, message: e.message });
                return res.send('fail');
            }
        } else {
            if (req.body.xml.result == 3) {
                logger.error('Payment failed!');
                insertLog('error', `${data.spbillno} Payment failed!`)
                io.sendPaidResult(data.spbillno, { code: 999, message: req.body.xml.retmsg });
            }
            return res.send('success');
        }
    } else {
        logger.error('Signature invalid!', req.body);
        insertLog('error', `${data.spbillno} Signature invalid! ${JSON.stringify(req.body)}`)
        io.sendPaidResult(data.spbillno, { code: 999, message: 'Signature invalid!' });
        return res.send('fail');
    }
}));

router.get('/nbhpaycb', wrap(function* (req, res) {
    logger.info(req.params);
    logger.info(req.body);
    logger.info(req.query);
    res.send('success');
}));

// 微信支付通知回调接口
router.post('/wxpaycb', require("./paymentcb/wxpaycb"))

/**
 * 查询交易记录
 * url query 参数：
 * apiType: 允许APP端指定用哪种方式查询历史记录，参数可以： sync_data, endpoint_data
 * actionType： action类型 transfer 转账（包括转出，转入）,transfer_in 只查询转入， transfer_out 只查询转出， others 其它交易
 * accountName： 哪个账号发生的交易
 * pageSize： 分页数据每一页多少条数据
 * rowIndex： 分页数据从第几行开始
 */
router.get('/query_eos_action', wrap(function* (req, res) {
    let apiType = req.query.apiType;  // apiType 允许APP端指定用哪种方式查询历史记录，参数可以： sync_data, endpoint_data

    // 刚开始 actionType 参数名使用 type， 后来改用 actionType， 下面一行兼容以前type的
    let actionType = req.query.actionType == null ? req.query.type : req.query.actionType;
    let accountName = req.query.accountName;
    let currency = req.query.currency;
    let pageSize = req.query.pageSize == null ? 10 : parseInt(req.query.pageSize);
    let rowIndex = req.query.rowIndex == null ? 0 : parseInt(req.query.rowIndex);

    if (actionType == null) {
        actionType = "transfer"; // 默认  transfer
    }

    let datas = {
        count: 0,
        last_irreversible_block: null,
        rows: []
    };

    try {
        datas = yield queryActionHisroty(apiType, actionType, accountName, currency, pageSize, rowIndex);
    } catch (e) {
        logger.error(e.message);
        res.send(datas);
    }

    res.send(datas);
}));

/**
 * 获取服务器上最新APP的版本信息
 * 接口接收一个参数：platform: [android 或 ios], 指定APP所在平台，目前暂时只支持android和ios两个平台
 */
router.get('/appverinfo', wrap(function* (req, res) {
    let platform = req.query.platform;
    let fs = require("fs");

    let verInfoPath = __dirname + '/apk/verinfo.json'; // 默认是
    if (platform === "ios") {
        verInfoPath = __dirname + '/ios/verinfo.json';
    }
    fs.exists(verInfoPath, function (exists) {
        if (exists) {
            res.sendFile(verInfoPath);
        } else {
            return res.sendStatus(404);
        }
    });
}));

/**
 * Deprecated 这个接口的功能已迁移到 router.get('/ewallet_app_file'...; 为了兼容旧的APP更新，暂且保留。
 *
 * 根据APP文件名( URL file参数 )下载APP
 */
router.get('/getapk', wrap(function* (req, res) {
    let apkFileName = req.query.file;
    if (apkFileName == null || apkFileName.length == 0) {
        return res.sendStatus(404);
    }
    let fs = require("fs");
    const apkFilePath = __dirname + '/apk/' + apkFileName;
    fs.exists(apkFilePath, function (exists) {
        if (exists) {
            res.sendFile(apkFilePath);
        } else {
            return res.sendStatus(404);
        }
    });
}));

/**
 * Deprecated 这个接口的功能已迁移到 router.get('/ewallet_app_latest'...; 为了兼容旧的APP更新，暂且保留。
 *
 * 直接获取最新的APK版本，提供下载
 */
router.get('/getlatestapk', wrap(function* (req, res) {
    try {
        let fs = require("fs");
        fs.readFile(__dirname + '/apk/verinfo.json', { encoding: "utf-8" }, function (err, info) {
            if (err) {
                logger.error(err);
                return res.sendStatus(404);
            } else {
                const apkVerInfo = JSON.parse(info);
                const apkFileName = apkVerInfo.file;
                if (apkFileName == null || apkFileName.length == 0) {
                    return res.sendStatus(404);
                }
                const apkFilePath = __dirname + '/apk/' + apkFileName;
                fs.exists(apkFilePath, function (exists) {
                    if (exists) {
                        res.sendFile(apkFilePath);
                    } else {
                        return res.sendStatus(404);
                    }
                });
            }
        });
    } catch (error) {
        logger.error(error);
        return res.sendStatus(404);
    }

}));


/**
 * 根据APP文件名( URL file参数 )下载APP
 * 接口接收一个参数 file ： APP安装文件名, 接口根据文件的扩展名判断返回apk或者ipa
 */
router.get('/ewallet_app_file', wrap(function* (req, res) {
    try {
        let appFileName = req.query.file;
        if (appFileName == null || appFileName.length == 0) {
            return res.sendStatus(404); // 没有指定file参数， 返回 not found
        }
        let fs = require("fs");

        let appFilePath = "";
        if (appFileName.toLowerCase().endsWith("apk")) {
            appFilePath = __dirname + '/apk/' + appFileName;
        } else if (appFileName.toLowerCase().endsWith("ipa")) {
            appFilePath = __dirname + '/ios/' + appFileName;
        } else {
            return res.sendStatus(404); // 不是预期的文件类型，返回 not found
        }
        fs.exists(appFilePath, function (exists) {
            if (exists) {
                res.sendFile(appFilePath);
            } else {
                return res.sendStatus(404); // 没有该文件，返回 not found
            }
        });
    } catch (error) {
        logger.error(error);
        return res.sendStatus(404); // 调用过程中所有的错误，均返回 not found
    }
}));


/**
 * 直接获取最新的版本，提供下载
 * 接口接收一个参数：platform: [android 或 ios], 指定APP所在平台，目前暂时只支持android和ios两个平台
 */
router.get('/ewallet_app_latest', wrap(function* (req, res) {
    try {
        let platform = req.query.platform;
        let verInfoPath = "";
        if (platform === "android" || platform === "apk") {
            verInfoPath = __dirname + "/apk/";
        } else if (platform === "ios") {
            verInfoPath = __dirname + "/ios/";
        } else {
            return res.sendStatus(404); // 不是预期的参数，返回 not found
        }

        let fs = require("fs");
        fs.readFile(verInfoPath + "verinfo.json", { encoding: "utf-8" }, function (err, info) {
            if (err) {
                logger.error(err);
                return res.sendStatus(404); // 读取info文件错误，返回 not found
            } else {
                const appVerInfo = JSON.parse(info);
                const appFileName = appVerInfo.file;
                if (appFileName == null || appFileName.length == 0) {
                    return res.sendStatus(404);
                }
                if (appFileName.indexOf("http://") === 0 || appFileName.indexOf("https://") === 0) {
                    // 下载地址可能是放在第三方
                    return res.redirect(appFileName);
                } else {
                    const appFilePath = verInfoPath + appFileName;
                    fs.exists(appFilePath, function (exists) {
                        if (exists) {
                            res.sendFile(appFilePath);
                        } else {
                            return res.sendStatus(404); // 没有该文件，返回 not found
                        }
                    });
                }
            }
        });
    } catch (error) {
        logger.error(error);
        return res.sendStatus(404); // 调用过程中所有的错误，均返回 not found
    }

}));

router.get('/paymentmethods', wrap(function* (req, res) {
    const { app_version = "1.0.0" } = req.query

    const paymentmethods = []
    for (let item of config[process.env.NODE_ENV].paymentMethods) {
        if (item.version) {
            if (semver.satisfies(app_version, `>=${item.version}`)) {
                paymentmethods.push(item)
            }
        } else {
            paymentmethods.push(item)
        }
    }
    res.json(paymentmethods)
}));

// 返回DApp列表
router.get('/dapplist', async (req, res) => {
    const { app_version = "0.0.0" } = req.query
    const refAccount = "jianmiwallet";
    const rootUrl = process.env.WEB_ROOT_URL;

    // 从数据库获取dapp列表
    let dAppList = await models.ThirdPartyApp.findAll({
        order: [['seqNum', 'ASC']],
        attributes: ['appType', 'platform', 'appCode', 'appName', 'appUrl', 'appIcon', 'gteVersion', 'lteVersion', 'uiOptions', 'chatDapp', 'seqNum'],
        raw: true
    })

    let retDAppList = []
    dAppList.map(val => {
        if (!val.platform || val.platform !== 'offline') {
            val.appIcon = `${rootUrl}${val.appIcon}`
            val.appUrl = val.appUrl.replace(/{refAccount}/g, refAccount)

            // 兼容最低版本
            const gteVersion = val.gteVersion || '0.0.0'
            // 判断是否设置了版本最大值, 若设置了, 则取版本范围, 未设置则取大于最低版本
            let versionRange = val.lteVersion ? `${gteVersion} - ${val.lteVersion}` : `>= ${gteVersion}`;

            if (semver.satisfies(app_version, versionRange)) {
                retDAppList.push(val)
            }
        }
    })
    retDAppList = _.groupBy(retDAppList, (val) => val.appType)

    // 兼容
    retDAppList.marketAppList = retDAppList.marketAppList || []
    retDAppList.gameAppList = retDAppList.gameAppList || []
    retDAppList.gameHotAppList = retDAppList.gameHotAppList || []
    retDAppList.gameNewAppList = retDAppList.gameNewAppList || []
    retDAppList.hotTgGroup = retDAppList.hotTgGroup || []
    retDAppList.toolsAppList = retDAppList.toolsAppList || []

    // 将新品 添加到gameAppList数组中 兼容以前版本
    const newAppList = retDAppList.gameNewAppList.filter((item, i) => {
        let isNew = true;
        retDAppList.gameAppList.forEach(element => {
            if (element.appName === item.appName) {
                isNew = false;
            }
        });
        // 如果以前的清单中没有这个新品就放到清单最前面
        if (isNew) {
            return item
        }
    })

    // 将热门 添加到gameAppList数组中 兼容以前版本
    const HotAppList = retDAppList.gameHotAppList.filter((item, i) => {
        let isNew = true;
        retDAppList.gameAppList.forEach(element => {
            if (element.appName === item.appName) {
                isNew = false;
            }
        });
        // 如果以前的清单中没有这个热门就放到清单最前面
        if (isNew) {
            return item
        }
    })

    // 新版本 热门显示新品以外的全部
    retDAppList.gameHotAppList = HotAppList.concat(retDAppList.gameAppList);

    // 兼容旧版本 显示所有
    retDAppList.gameAppList = newAppList.concat(HotAppList).concat(retDAppList.gameAppList);

    res.send(retDAppList);
});

router.post('/feedback', wrap(function* (req, res) {
    const token = req.body.token || req.query.token;
    if (!token) {
        logger.error('feedback api: no token');
        return res.sendStatus(404);  // 没有安全token验证字段, 可能是恶意请求，返回 not found
    }
    const data = {
        dataType: 'feedback',
        email: req.body.email || req.query.email,
        accountName: req.body.accountName || req.query.accountName,
        appVersion: req.body.appVersion || req.query.appVersion,
        content: req.body.content || req.query.content,
        timestamp: req.body.timestamp || req.query.timestamp,
    }
    if (!data.email || !data.content || !data.timestamp) {
        logger.error('feedback api: no params');
        return res.sendStatus(404);; // 参数不全，返回 not found
    }
    try {
        const signStr = `feedback|${data.email}|${data.timestamp}|${process.env.API_SIGN_KEY}`
        const sign = crypto.createHash('sha256').update(signStr).digest('hex').toUpperCase();
        if (sign !== token) {
            logger.error('feedback api: error token, corrected=' + sign + ' for ' + signStr);
            return res.sendStatus(404);; // API 访问验证通不过，返回 not found
        }
        const result = yield appDataCollection.handleCollectionData(data);
        return res.json({ result: result });

    } catch (error) {
        logger.error(error);
        // return res.send('error');
        return res.sendStatus(500); //
    }
}));

router.post('/subscribe', wrap(function* (req, res) {
    const token = req.body.token || req.query.token;
    if (!token) {
        logger.error('subscribe api: no token');
        return res.sendStatus(404);  // 没有安全token验证字段, 可能是恶意请求，返回 not found
    }
    const data = {
        dataType: 'subscribe',
        email: req.body.email || req.query.email,
        accountName: req.body.accountName || req.query.accountName,
        appVersion: req.body.appVersion || req.query.appVersion,
        timestamp: req.body.timestamp || req.query.timestamp,
    }
    if (!data.email || !data.timestamp) {
        logger.error('subscribe api: no params');
        return res.sendStatus(404);; // 参数不全，返回 not found
    }
    try {
        const signStr = `subscribe|${data.email}|${data.timestamp}|${process.env.API_SIGN_KEY}`
        const sign = crypto.createHash('sha256').update(signStr).digest('hex').toUpperCase();
        if (sign !== token) {
            logger.error('subscribe api: error token, corrected=' + sign + ' for ' + signStr);
            return res.sendStatus(404);; // API 访问验证通不过，返回 not found
        }
        const result = yield appDataCollection.handleCollectionData(data);
        return res.json({ result: result });

    } catch (error) {
        logger.error(error);
        // return res.send('error');
        return res.sendStatus(500); //
    }
}));

/**
 * 下载订阅邮箱列表：
 * token 简单的访问验证，固定为：key4JM_20181001_1236985
 * columns： 空时只提供下载邮箱列表， 可以下载其它的列: subscribe_time,app_version,account_name
 * count：下载最新多少条数据，空时下载最新100条
 * sp: 下载的txt文件， 假如有多列数据，每列分隔符， 空时默认用逗号分隔
 */
router.get('/subscribe_mail_list', wrap(function* (req, res) {
    const token = req.query.token;
    const columns = req.query.columns || '';
    let count = parseInt(req.query.count || 100);
    if (!count) {
        count = 100;
    }
    const sp = req.query.sp || ',';
    if (token !== 'key4JM_20181001_1236985') {
        return res.sendStatus(404); //
    }

    try {
        const datalist = yield appDataCollection.findSubscribeList({ count: count });
        let result = '';
        for (const item of datalist) {
            result += item.email;
            if (columns.indexOf('subscribe_time') !== -1) {
                result += sp + moment(item.createdAt).format('YYYYMMDDhhmmss');
            }
            if (columns.indexOf('app_version') !== -1) {
                result += sp + item.appVersion;
            }
            if (columns.indexOf('account_name') !== -1) {
                result += sp + item.accountName;
            }
            result += '\n';
        }
        res.setHeader('Content-disposition', 'attachment; filename=subscribe_mail_list.txt');
        res.setHeader('Content-type', 'text/plain');
        res.write(result); //
        res.end();
        return;
    } catch (error) {
        logger.error(error);
        return res.sendStatus(500); //
    }
}));

router.get("/end_point_list", async (req, res) => {
    const endpointList = config[process.env.NODE_ENV].endpointList
    res.json(endpointList)
})


router.get("/pay/wxpay", async (req, res) => {
    // const ret = await require("../lib/wxPay").wxPay("20181806125359", 0.01, "http://test.jmi.one/wxpaycb")
    // res.json(ret)

    const { code_url, total_fee } = req.query
    const qrImgUrl = await qrcode.toDataURL(code_url);
    res.render("wxpay", {
        qrImgUrl: qrImgUrl,
        total_fee: total_fee
    })
})

// -------------以下的路由均已模块化-----------------

// 管理后台用户登录
router.use("/auth", require("./auth/router"))

// 后台管理模块接口
router.use("/manage", require("./manage/router"))

// Exchange API
router.use("/exchange", require("./exchange"))

module.exports = router;